/*
 * Copyright 2011-2012 by The Regents of the University of California Licensed
 * under the Apache License, Version 2.0 (the "License"); you may not use this
 * file except in compliance with the License. you may obtain a copy of the
 * License from
 * 
 * http://www.apache.org/licenses/LICENSE-2.0
 * 
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
 * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
 * License for the specific language governing permissions and limitations under
 * the License.
 */
package edu.ucsd.db.bassarisk.query.bindingtree.visitor;

import edu.ucsd.db.bassarisk.exception.UnexpectedCaseException;
import edu.ucsd.db.bassarisk.query.binding.Bindings.Binding;
import edu.ucsd.db.bassarisk.query.bindingtree.BindingTreeElements.BindingTreeEdge;
import edu.ucsd.db.bassarisk.query.bindingtree.BindingTreeElements.ChoiceNode;
import edu.ucsd.db.bassarisk.query.bindingtree.BindingTreeElements.ChoiceNode.Choice;
import edu.ucsd.db.bassarisk.query.bindingtree.BindingTreeElements.ChoiceNode.ChooseAll;
import edu.ucsd.db.bassarisk.query.bindingtree.BindingTreeElements.ChoiceNode.ChooseOne;
import edu.ucsd.db.bassarisk.query.bindingtree.BindingTreeElements.EstablishingNode;
import edu.ucsd.db.bassarisk.treepattern.EdgeFunction;

public class BindingTreeFormattingTraversal implements
    BindingTreeVisitor<Void, BindingTreeFormattingTraversal.Args> {
  public static class Args {
    private int indent = 0;
    private final StringBuilder sb;

    public Args(final StringBuilder sb) {
      this.sb = sb;
    }

    private void closeBlock() {
      this.indent--;
      this.newLine();
      this.sb.append("}");
    }

    private void newLine() {
      this.sb.append("\n");
      for (int i = 0; i < this.indent; ++i) {
        this.sb.append("    ");
      }
    }

    private void openBlock() {
      this.indent++;
      this.sb.append(" {");
      this.newLine();
    }
  }

  private void formatIntersection(final Iterable<BindingTreeEdge> edges,
                                  final Args args) {
    args.openBlock();
    boolean first = true;
    for (BindingTreeEdge child : edges) {
      if (first) {
        first = false;
        args.sb.append("  ");
      } else {
        args.newLine();
        args.sb.append("& ");
      }
      child.accept(this, args);
    }
    args.closeBlock();
  }

  @Override
  public Void visitBindingTreeEdge(final BindingTreeEdge edge, final Args args) {
    StringBuilder sb = args.sb;
    sb.append("//");
    for (EdgeFunction edgeFunction : edge.functions()) {
      switch (edgeFunction) {
      case Count:
        sb.append("#");
        break;
      case Empty:
        sb.append("∉");
        break;
      case Mean:
        sb.append("~");
        break;
      case NonEmpty:
        sb.append("∈");
        break;
      case Product:
        sb.append("×");
        break;
      case Sum:
        sb.append("+");
        break;
      case Values:
        sb.append("=");
        break;
      default:
        throw UnexpectedCaseException.ofEnum(EdgeFunction.class);
      }
      sb.append(":");
    }
    edge.tailNode().accept(this, args);
    return null;
  }

  @Override
  public Void visitChoiceNode(final ChoiceNode node, final Args args) {
    StringBuilder sb = args.sb;
    Iterable<Choice> choices = node.getChoices();
    sb.append("UNION");
    args.openBlock();
    boolean first = true;
    for (Choice choice : choices) {
      if (first) {
        first = false;
        sb.append("  ");
      } else {
        args.newLine();
        sb.append("| ");
      }
      if (choice instanceof ChooseOne) {
        ((ChooseOne) choice).getChild().accept(this, args);
      } else if (choice instanceof ChooseAll) {
        sb.append("AND");
        this.formatIntersection(((ChooseAll) choice).getChildren(), args);
      }
    }
    args.closeBlock();
    return null;
  }

  @Override
  public Void visitEstablishingNode(final EstablishingNode node, final Args args) {
    StringBuilder sb = args.sb;
    Binding established = node.getEstablishedBinding();
    String format = "%s:%s";
    sb.append(String.format(format, established.getId(), established
        .boundSequenceClass().getName()));
    Iterable<BindingTreeEdge> children = node.children();
    if (node.outDegree() > 1) {
      this.formatIntersection(children, args);
    } else {
      for (BindingTreeEdge child : children) {
        child.accept(this, args);
      }
    }
    return null;
  }
}
